package docbookarchivemanager.converter;

import docbookarchivemanager.utils.DomManager;
import docbookarchivemanager.utils.DocbookFile;
import docbookarchivemanager.utils.FileManager;
import docbookarchivemanager.converter.elements.Attribute;
import docbookarchivemanager.converter.elements.Data;
import docbookarchivemanager.converter.elements.Element;
import docbookarchivemanager.converter.elements.OutputFolder;
import docbookarchivemanager.utils.StringHelper;
import docbookarchivemanager.utils.Stylesheet;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

/**
 *
 * @author Jakub Hrachovec
 */
public class ReplacementManager {

    private static ArrayList<OutputFolder> dataToSearch = Data.getDataToSearch();
    private static ArrayList<OutputFolder> dataToSearch_xsl = Data.getDataToSearch_xsl();

    /**
     * Processes said Docbook XML or XSL file.
     *
     * @param dbFile
     * @throws NullPointerException
     * @throws java.io.IOException
     */
    public static void processFile(DocbookFile dbFile) throws NullPointerException, IOException {
        File file = dbFile.getFile();

        if (file.exists()) {
            try {
                //Where this file is going to be outputted.
                File newFile = new File(dbFile.getAbsolutePath());

                //Create all ancestor directories if they do not already exist.
                newFile.getParentFile().mkdirs();

                //Search through the document, replace values, add files for procession, and return modified DOM.
                Document doc = search(dbFile, newFile);

                //Write the DOM to given location.
                DomManager.writeDom(doc, newFile.getCanonicalPath());

            } catch (IOException | NullPointerException ex) {
                if (dbFile.IsIncluded()) {
                    System.out.println(String.format("ERROR: Ignoring file: %s.", file.getAbsolutePath()));
                } else {
                    throw new IOException(String.format("%s is corrupted. Cannot proceed.", file.getName()));
                }
            }

        }
    }

    /**
     * Searches the DOM of given file, add files to be archived from it, replaces
     * node values, and return modified DOM.
     *
     * @param dbFile Docbook XML or an XSL file.
     * @param newFile Outputted file's new location within the archive.
     * @throws IOException
     * @return Modified DOM.
     */
    private static Document search(DocbookFile dbFile, File newFile) throws IOException, NullPointerException {
        //Get the file's DOM.
        Document doc = DomManager.readDom(dbFile.getFile());
        //Check the file for PIs.
        checkForProcessingInstructions(doc, dbFile);
        ArrayList<OutputFolder> dataToSearchHere = null;
        //Set data (elements, attributes) to search for depending on type of the file.
        if (DocbookFile.Type.XML.equals(dbFile.getType())) {
            dataToSearchHere = dataToSearch;
        } else if (DocbookFile.Type.XSL.equals(dbFile.getType())) {
            dataToSearchHere = dataToSearch_xsl;
        }
        if (dataToSearchHere != null) {
            System.out.println("Processing file: " + dbFile.getFile().getName() + ".");
            for (OutputFolder out : dataToSearchHere) {
                String defaultOutputFolderName = out.getFolderName();
                for (Element ele : out.getElements()) {
                    String namespacePrefix = getNsPrefix(ele);
                    for (Attribute att : ele.getAttributes()) {
                        NodeList nodes = DomManager.evaluateXpath(doc, "//" + namespacePrefix + ele.getName() + "/@" + att.getName());
                        if (nodes != null) {
                            for (int i = 0; i < nodes.getLength(); i++) {
                                Node curNode = nodes.item(i);
                                String curNodeValue = curNode.getNodeValue().trim();
                                String newNodeValue = null;
                                File curNodeFile;
                                String outputFolderName;

                                //Check if the file we are searching is a Docbook XML.
                                if (dbFile.getType() == DocbookFile.Type.XML) {

                                    //Check if it referenes an XInclude.
                                    if (isXInclude(ele.getName(), att.getName(), ele.getNamespace())) {
                                        //If the file's content type is a valid one then add it for processing and change the node value.
                                        curNodeFile = FileManager.getLocalXmlFile(curNodeValue, dbFile.getFile());

                                        if (curNodeFile != null) {
                                            outputFolderName = FileManager.getOutputFolder(curNodeFile, defaultOutputFolderName);
                                            newNodeValue = Converter.processFile(curNodeFile, outputFolderName, newFile, DocbookFile.Type.XML);
                                        }

                                    } else {
                                        //If a file isn't included just rename it, add it for copying, and change the node value.
                                        curNodeFile = FileManager.getLocalFile(curNodeValue, dbFile.getFile());

                                        if (curNodeFile != null) {
                                            outputFolderName = FileManager.getOutputFolder(curNodeFile, defaultOutputFolderName);
                                            newNodeValue = Converter.processFile(curNodeFile, outputFolderName, newFile, null);
                                        }

                                    }

                                    //Check if the file we are searching is an XSL.
                                } else if (dbFile.getType() == DocbookFile.Type.XSL) {
                                    //Check if it referenes an xsl:import or an xsl:include.
                                    if (isStylesheet(ele.getName(), att.getName(), ele.getNamespace())) {
                                        curNodeFile = FileManager.getLocalXmlFile(curNodeValue, dbFile.getFile());

                                        if (curNodeFile != null) {
                                            outputFolderName = FileManager.getOutputFolder(curNodeFile, defaultOutputFolderName);
                                            newNodeValue = Converter.processFile(curNodeFile, outputFolderName, newFile, DocbookFile.Type.XSL);
                                        }
                                    }
                                }
                                //Sets the node value so it points to the file within archive.
                                if (newNodeValue != null) {
                                    curNode.setNodeValue(newNodeValue);
                                }
                            }
                        }
                    }
                }
            }
        }
        return doc;
    }

    private static void checkForProcessingInstructions(Document doc, DocbookFile dbFile) throws IOException, NullPointerException {
        //Check if we are dealing with an XML or XSL file.
        if (dbFile.getType() == DocbookFile.Type.XML) {
            //Check for the XML-stylesheet PI.
            processInstruction("/processing-instruction('xml-stylesheet')", doc, dbFile);
            processInstruction("/processing-instruction('dbhtml-include')", doc, dbFile);
        }

    }

    //Checks for PIs depending on the type of given file.
    private static void processInstruction(String xpath, Document doc, DocbookFile dbFile) throws IOException, NullPointerException {
        String hrefRegex = "(\\s+)?href(\\s+)?=(\\s+)?\"(\\s+)?(.*?)(\\s+)?\"(\\s+)?";
        NodeList nodes = DomManager.evaluateXpath(doc, xpath);
        if (nodes != null) {
            for (int i = 0; i < nodes.getLength(); i++) {
                Node curNode = nodes.item(i);
                String curNodeValue = curNode.getNodeValue().trim();
                File curNodeFile;
                String href;
                String newFilePath;
                String outputFolderName;
                String newNodeValue = null;
                //Check if the PI refers an XSL stylesheet 
                if (xpath.contains("xml-stylesheet")) {
                    if (StringHelper.containsStylesheet(curNodeValue)) {
                        href = StringHelper.getValue(curNodeValue, hrefRegex, 5);
                        if (href != null) {
                            href = StringHelper.decodeURL(href.trim());

                            if (StringHelper.isValidURL(href)) {
                                if (!dbFile.IsIncluded()) {
                                    Converter.addStylesheet(new Stylesheet(href, FileManager.getFormatObject(href), href.substring(href.lastIndexOf("/") + 1), Stylesheet.Type.PI));
                                }
                            } else {
                                curNodeFile = FileManager.getLocalXmlFile(href, dbFile.getFile());
                                if (curNodeFile != null) {
                                    outputFolderName = FileManager.getOutputFolder(curNodeFile, DocbookFile.Type.XSL.getPath());
                                    Stylesheet.Type type = null;
                                    if (!dbFile.IsIncluded()) {
                                        type = Stylesheet.Type.PI;
                                    }
                                    newFilePath = Converter.processFile(curNodeFile, outputFolderName, new File(dbFile.getAbsolutePath()), DocbookFile.Type.XSL, true, type, FileManager.getFormatObject(curNodeFile));
                                    newNodeValue = getNewPIValue(curNode, href, newFilePath.replaceAll("\\\\", "\\\\\\\\"));
                                }
                            }

                        }
                    }
                } //Check for "dbhtml-include" PI and copy the file it points to.
                else if (xpath.contains("dbhtml-include")) {
                    href = StringHelper.getValue(curNodeValue, hrefRegex, 5);
                    if (href != null) {
                        href = StringHelper.decodeURL(href.trim());
                        curNodeFile = FileManager.getLocalFile(href, dbFile.getFile());
                        if (curNodeFile != null) {
                            outputFolderName = FileManager.getOutputFolder(curNodeFile, DocbookFile.Type.XML.getPath());
                            newFilePath = Converter.processFile(curNodeFile, outputFolderName, new File(dbFile.getAbsolutePath()), null, true);
                            newNodeValue = getNewPIValue(curNode, href, newFilePath.replaceAll("\\\\", "\\\\\\\\"));
                        }
                    }
                }
                if (newNodeValue != null) {
                    curNode.setNodeValue(newNodeValue);
                }
            }
        }
    }

    private static String getNewPIValue(Node curNode, String oldValue, String newValue) {
        String curNodeValue = curNode.getNodeValue().trim();
        Pattern pattern = Pattern.compile(oldValue);
        Matcher matcher = pattern.matcher(curNodeValue);
        String newNodeValue = matcher.replaceAll(newValue);
        return newNodeValue;

    }

    private static boolean isXInclude(String eleName, String attName, String ns) {
        return ("include".equals(eleName) && "href".equals(attName) && "http://www.w3.org/2001/XInclude".equals(ns));
    }

    private static boolean isStylesheet(String eleName, String attName, String ns) {
        return (("import".equals(eleName) || "include".equals(eleName)) && "href".equals(attName) && "http://www.w3.org/1999/XSL/Transform".equals(ns));

    }

    private static String getNsPrefix(Element ele) {
        if (isNamespaceValid(ele)) {
            return ele.getNamespace_prefix() + ":";
        } else {
            return "";
        }
    }

    private static boolean isNamespaceValid(Element ele) {
        return ele.getNamespace_prefix() != null && !"".equals(ele.getNamespace_prefix()) && ele.getNamespace() != null && !"".equals(ele.getNamespace());

    }
}
